﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using SR = SocketHttpLib.SystemSR;
namespace SocketHttpLib
{
    internal sealed class ChunkParser
    {
        private enum ReadState
        {
            ChunkLength,
            Extension,
            Payload,
            PayloadEnd,
            Trailer,
            Done,
            Error
        }

        private const int chunkLengthBuffer = 12;

        private const int noChunkLength = -1;

        private static readonly bool[] tokenChars;

        private byte[] buffer;

        private int bufferCurrentPos;

        private int bufferFillLength;

        private int maxBufferLength;

        private byte[] userBuffer;

        private int userBufferOffset;

        private int userBufferCount;

        private IAsyncResult userAsyncResult;

        private Stream dataSource;

        private ChunkParser.ReadState readState;

        private int totalTrailerHeadersLength;

        private int currentChunkLength;

        private int currentChunkBytesRead;

        private int currentOperationBytesRead;

        private int syncResult;

        private bool IsAsync
        {
            get
            {
                return this.userAsyncResult != null;
            }
        }

        static ChunkParser()
        {
            ChunkParser.tokenChars = new bool[128];
            for (int i = 33; i < 127; i++)
            {
                ChunkParser.tokenChars[i] = true;
            }
            ChunkParser.tokenChars[40] = false;
            ChunkParser.tokenChars[41] = false;
            ChunkParser.tokenChars[60] = false;
            ChunkParser.tokenChars[62] = false;
            ChunkParser.tokenChars[64] = false;
            ChunkParser.tokenChars[44] = false;
            ChunkParser.tokenChars[59] = false;
            ChunkParser.tokenChars[58] = false;
            ChunkParser.tokenChars[92] = false;
            ChunkParser.tokenChars[34] = false;
            ChunkParser.tokenChars[47] = false;
            ChunkParser.tokenChars[91] = false;
            ChunkParser.tokenChars[93] = false;
            ChunkParser.tokenChars[63] = false;
            ChunkParser.tokenChars[61] = false;
            ChunkParser.tokenChars[123] = false;
            ChunkParser.tokenChars[125] = false;
        }

        public ChunkParser(Stream dataSource, byte[] internalBuffer, int initialBufferOffset, int initialBufferCount, int maxBufferLength)
        {
            this.dataSource = dataSource;
            this.buffer = internalBuffer;
            this.bufferCurrentPos = initialBufferOffset;
            this.bufferFillLength = initialBufferOffset + initialBufferCount;
            this.maxBufferLength = maxBufferLength;
            this.currentChunkLength = -1;
            this.readState = ChunkParser.ReadState.ChunkLength;
        }

        public IAsyncResult ReadAsync(object caller, byte[] userBuffer, int userBufferOffset, int userBufferCount, AsyncCallback callback, object state)
        {
            this.SetReadParameters(userBuffer, userBufferOffset, userBufferCount);
            this.userAsyncResult = SystemSR.CreateInstance("System.Net.LazyAsyncResult", caller, state, callback) as IAsyncResult;
            IAsyncResult result = this.userAsyncResult;
            try
            {
                this.ProcessResponse();
            }
            catch (Exception result2)
            {
                this.CompleteUserRead(result2);
            }
            return result;
        }

        public int Read(byte[] userBuffer, int userBufferOffset, int userBufferCount)
        {
            this.SetReadParameters(userBuffer, userBufferOffset, userBufferCount);
            try
            {
                this.ProcessResponse();
            }
            catch (Exception)
            {
                this.TransitionToErrorState();
                throw;
            }
            return this.syncResult;
        }

        private void SetReadParameters(byte[] userBuffer, int userBufferOffset, int userBufferCount)
        {
            if (Interlocked.CompareExchange<byte[]>(ref this.userBuffer, userBuffer, null) != null)
            {
                throw new InvalidOperationException(SR.GetString("net_inasync"));
            }
            this.userBufferCount = userBufferCount;
            this.userBufferOffset = userBufferOffset;
        }

        public bool TryGetLeftoverBytes(out byte[] buffer, out int leftoverBufferOffset, out int leftoverBufferSize)
        {
            leftoverBufferOffset = 0;
            leftoverBufferSize = 0;
            buffer = null;
            if (this.readState != ChunkParser.ReadState.Done)
            {
                return false;
            }
            if (this.bufferCurrentPos == this.bufferFillLength)
            {
                return false;
            }
            leftoverBufferOffset = this.bufferCurrentPos;
            leftoverBufferSize = this.bufferFillLength - this.bufferCurrentPos;
            buffer = this.buffer;
            return true;
        }

        private void ProcessResponse()
        {
            while (this.readState < ChunkParser.ReadState.Done)
            {
                DataParseStatus dataParseStatus;
                switch (this.readState)
                {
                    case ChunkParser.ReadState.ChunkLength:
                        dataParseStatus = this.ParseChunkLength();
                        break;
                    case ChunkParser.ReadState.Extension:
                        dataParseStatus = this.ParseExtension();
                        break;
                    case ChunkParser.ReadState.Payload:
                        dataParseStatus = this.HandlePayload();
                        break;
                    case ChunkParser.ReadState.PayloadEnd:
                        dataParseStatus = this.ParsePayloadEnd();
                        break;
                    case ChunkParser.ReadState.Trailer:
                        dataParseStatus = this.ParseTrailer();
                        break;
                    default:
                        throw new InternalException();
                }
                switch (dataParseStatus)
                {
                    case DataParseStatus.NeedMoreData:
                        if (!this.TryGetMoreData())
                        {
                            return;
                        }
                        break;
                    case DataParseStatus.ContinueParsing:
                        break;
                    case DataParseStatus.Done:
                        return;
                    case DataParseStatus.Invalid:
                    case DataParseStatus.DataTooBig:
                        this.CompleteUserRead(new IOException(SR.GetString("net_io_readfailure", new object[]
                        {
                        SR.GetString("net_io_connectionclosed")
                        })));
                        return;
                    default:
                        throw new InternalException();
                }
            }
        }

        private void CompleteUserRead(object result)
        {
            bool flag = result is Exception;
            this.userBuffer = null;
            this.userBufferCount = 0;
            this.userBufferOffset = 0;
            if (flag)
            {
                this.TransitionToErrorState();
            }
            if (this.IsAsync)
            {
                IAsyncResult lazyAsyncResult = this.userAsyncResult;
                this.userAsyncResult = null;
                lazyAsyncResult.GetType().GetMethod("InvokeCallback").Invoke(lazyAsyncResult, new object[] { result });
                //lazyAsyncResult.InvokeCallback(result);
                return;
            }
            if (flag)
            {
                throw result as Exception;
            }
            this.syncResult = (int)result;
        }

        private void TransitionToErrorState()
        {
            this.readState = ChunkParser.ReadState.Error;
        }

        private bool TryGetMoreData()
        {
            this.PrepareBufferForMoreData();
            int num = this.buffer.Length - this.bufferFillLength;
            if (this.readState == ChunkParser.ReadState.ChunkLength)
            {
                num = Math.Min(12, num);
            }
            int bytesRead;
            if (this.IsAsync)
            {
                IAsyncResult asyncResult = this.dataSource.BeginRead(this.buffer, this.bufferFillLength, num, new AsyncCallback(this.ReadCallback), null);
                this.CheckAsyncResult(asyncResult);
                if (!asyncResult.CompletedSynchronously)
                {
                    return false;
                }
                bytesRead = this.dataSource.EndRead(asyncResult);
            }
            else
            {
                bytesRead = this.dataSource.Read(this.buffer, this.bufferFillLength, num);
            }
            this.CompleteMetaDataReadOperation(bytesRead);
            return true;
        }

        private void PrepareBufferForMoreData()
        {
            int num = this.bufferCurrentPos;
            this.bufferCurrentPos = 0;
            if (num == this.bufferFillLength)
            {
                this.bufferFillLength = 0;
                return;
            }
            if (num > 0 || this.bufferFillLength < this.buffer.Length)
            {
                if (num > 0)
                {
                    int count = this.bufferFillLength - num;
                    Buffer.BlockCopy(this.buffer, num, this.buffer, 0, count);
                    this.bufferFillLength = count;
                }
                return;
            }
            if (this.buffer.Length == this.maxBufferLength)
            {
                throw new IOException(SR.GetString("net_io_readfailure", new object[]
                {
                    SR.GetString("net_io_connectionclosed")
                }));
            }
            int num2 = Math.Min(this.maxBufferLength, this.buffer.Length * 2);
            byte[] dst = new byte[num2];
            Buffer.BlockCopy(this.buffer, 0, dst, 0, this.buffer.Length);
            this.buffer = dst;
        }

        private void CheckAsyncResult(IAsyncResult ar)
        {
            if (ar == null)
            {
                throw new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled);
            }
        }

        private void CompleteMetaDataReadOperation(int bytesRead)
        {
            if (bytesRead == 0)
            {
                throw new IOException(SR.GetString("net_io_readfailure", new object[]
                {
                    SR.GetString("net_io_connectionclosed")
                }));
            }
            this.bufferFillLength += bytesRead;
        }

        public void ReadCallback(IAsyncResult ar)
        {
            if (ar.CompletedSynchronously)
            {
                return;
            }
            try
            {
                int bytesRead = this.dataSource.EndRead(ar);
                if (this.readState == ChunkParser.ReadState.Payload)
                {
                    this.CompletePayloadReadOperation(bytesRead);
                }
                else
                {
                    this.CompleteMetaDataReadOperation(bytesRead);
                    this.ProcessResponse();
                }
            }
            catch (Exception result)
            {
                this.CompleteUserRead(result);
            }
        }

        private DataParseStatus HandlePayload()
        {
            if (this.bufferCurrentPos < this.bufferFillLength)
            {
                int num = Math.Min(Math.Min(this.userBufferCount, this.bufferFillLength - this.bufferCurrentPos), this.currentChunkLength - this.currentChunkBytesRead);
                Buffer.BlockCopy(this.buffer, this.bufferCurrentPos, this.userBuffer, this.userBufferOffset, num);
                this.bufferCurrentPos += num;
                if (this.currentChunkBytesRead + num == this.currentChunkLength || num == this.userBufferCount)
                {
                    this.CompletePayloadReadOperation(num);
                    return DataParseStatus.Done;
                }
                this.currentOperationBytesRead += num;
                this.currentChunkBytesRead += num;
            }
            int count = Math.Min(this.userBufferCount - this.currentOperationBytesRead, this.currentChunkLength - this.currentChunkBytesRead);
            if (this.IsAsync)
            {
                IAsyncResult asyncResult = this.dataSource.BeginRead(this.userBuffer, this.userBufferOffset + this.currentOperationBytesRead, count, new AsyncCallback(this.ReadCallback), null);
                this.CheckAsyncResult(asyncResult);
                if (asyncResult.CompletedSynchronously)
                {
                    this.CompletePayloadReadOperation(this.dataSource.EndRead(asyncResult));
                }
            }
            else
            {
                int bytesRead = this.dataSource.Read(this.userBuffer, this.userBufferOffset + this.currentOperationBytesRead, count);
                this.CompletePayloadReadOperation(bytesRead);
            }
            return DataParseStatus.Done;
        }

        private void CompletePayloadReadOperation(int bytesRead)
        {
            if (bytesRead == 0)
            {
                throw new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed), WebExceptionStatus.ConnectionClosed);
            }
            this.currentChunkBytesRead += bytesRead;
            int num = this.currentOperationBytesRead + bytesRead;
            if (this.currentChunkBytesRead == this.currentChunkLength)
            {
                this.readState = ChunkParser.ReadState.PayloadEnd;
            }
            this.currentOperationBytesRead = 0;
            this.CompleteUserRead(num);
        }

        private DataParseStatus ParseChunkLength()
        {
            int num = -1;
            int i = this.bufferCurrentPos;
            while (i < this.bufferFillLength)
            {
                byte b = this.buffer[i];
                if ((b < 48 || b > 57) && (b < 65 || b > 70) && (b < 97 || b > 102))
                {
                    if (num == -1)
                    {
                        return DataParseStatus.Invalid;
                    }
                    this.bufferCurrentPos = i;
                    this.currentChunkLength = num;
                    this.readState = ChunkParser.ReadState.Extension;
                    return DataParseStatus.ContinueParsing;
                }
                else
                {
                    int b2 = (b < 65) ? (b - 48) : (10 + ((b < 97) ? (b - 65) : (b - 97)));
                    if (num == -1)
                    {
                        num = b2;
                    }
                    else
                    {
                        if (num >= 134217728)
                        {
                            return DataParseStatus.Invalid;
                        }
                        num = (num << 4) + (int)b2;
                    }
                    i++;
                }
            }
            return DataParseStatus.NeedMoreData;
        }

        private DataParseStatus ParseExtension()
        {
            int num = this.bufferCurrentPos;
            DataParseStatus dataParseStatus = this.ParseWhitespaces(ref num);
            if (dataParseStatus != DataParseStatus.ContinueParsing)
            {
                return dataParseStatus;
            }
            dataParseStatus = this.ParseExtensionNameValuePairs(ref num);
            if (dataParseStatus != DataParseStatus.ContinueParsing)
            {
                return dataParseStatus;
            }
            dataParseStatus = this.ParseCRLF(ref num);
            if (dataParseStatus != DataParseStatus.ContinueParsing)
            {
                return dataParseStatus;
            }
            this.bufferCurrentPos = num;
            if (this.currentChunkLength == 0)
            {
                this.readState = ChunkParser.ReadState.Trailer;
            }
            else
            {
                this.readState = ChunkParser.ReadState.Payload;
            }
            return DataParseStatus.ContinueParsing;
        }

        private DataParseStatus ParsePayloadEnd()
        {
            DataParseStatus dataParseStatus = this.ParseCRLF(ref this.bufferCurrentPos);
            if (dataParseStatus != DataParseStatus.ContinueParsing)
            {
                return dataParseStatus;
            }
            this.currentChunkLength = -1;
            this.currentChunkBytesRead = 0;
            this.readState = ChunkParser.ReadState.ChunkLength;
            return DataParseStatus.ContinueParsing;
        }

        private DataParseStatus ParseTrailer()
        {
            if (this.ParseWhitespaces(ref this.bufferCurrentPos) == DataParseStatus.NeedMoreData)
            {
                return DataParseStatus.NeedMoreData;
            }
            int num = this.bufferCurrentPos;
            WebParseError webParseError;
            webParseError.Section = WebParseErrorSection.Generic;
            webParseError.Code = WebParseErrorCode.Generic;
            WebHeaderCollection webHeaderCollection = new WebHeaderCollection();
            DataParseStatus dataParseStatus;
            var section = SystemSR.GetStaticNonPublicField("System.Net.SettingsSectionInternal", "Section");
            var useUnsafeHeaderParsing = (bool)section.GetType().GetProperty("UseUnsafeHeaderParsing", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(section, null);
            //if (SettingsSectionInternal.Section.UseUnsafeHeaderParsing)
            if (useUnsafeHeaderParsing)
            {
                //dataParseStatus = webHeaderCollection.ParseHeaders(this.buffer, this.bufferFillLength, ref num, ref this.totalTrailerHeadersLength, this.maxBufferLength, ref webParseError);
                dataParseStatus = (DataParseStatus)SR.InvokeInstanceNonPublicMethod(webHeaderCollection, "ParseHeaders", new object[] { this.buffer, this.bufferFillLength, num, this.totalTrailerHeadersLength, this.maxBufferLength, webParseError });
            }
            else
            {
                //dataParseStatus = webHeaderCollection.ParseHeadersStrict(this.buffer, this.bufferFillLength, ref num, ref this.totalTrailerHeadersLength, this.maxBufferLength, ref webParseError);
                dataParseStatus = (DataParseStatus)SR.InvokeInstanceNonPublicMethod(webHeaderCollection, "ParseHeadersStrict", new object[] { this.buffer, this.bufferFillLength, num, this.totalTrailerHeadersLength, this.maxBufferLength, webParseError });
            }
            if (dataParseStatus == DataParseStatus.NeedMoreData || dataParseStatus == DataParseStatus.Done)
            {
                this.bufferCurrentPos = num;
            }
            if (dataParseStatus != DataParseStatus.Done)
            {
                return dataParseStatus;
            }
            this.readState = ChunkParser.ReadState.Done;
            this.CompleteUserRead(0);
            return DataParseStatus.Done;
        }

        private DataParseStatus ParseCRLF(ref int pos)
        {
            if (pos + 2 > this.bufferFillLength)
            {
                return DataParseStatus.NeedMoreData;
            }
            if (this.buffer[pos] != 13 || this.buffer[pos + 1] != 10)
            {
                return DataParseStatus.Invalid;
            }
            pos += 2;
            return DataParseStatus.ContinueParsing;
        }

        private DataParseStatus ParseWhitespaces(ref int pos)
        {
            for (int i = pos; i < this.bufferFillLength; i++)
            {
                byte c = this.buffer[i];
                if (!ChunkParser.IsWhiteSpace(c))
                {
                    pos = i;
                    return DataParseStatus.ContinueParsing;
                }
            }
            return DataParseStatus.NeedMoreData;
        }

        private static bool IsWhiteSpace(byte c)
        {
            return c == 32 || c == 9;
        }

        private DataParseStatus ParseExtensionNameValuePairs(ref int pos)
        {
            int num = pos;
            while (this.buffer[num] == 59)
            {
                num++;
                DataParseStatus dataParseStatus = this.ParseWhitespaces(ref num);
                if (dataParseStatus != DataParseStatus.ContinueParsing)
                {
                    return dataParseStatus;
                }
                dataParseStatus = this.ParseToken(ref num);
                if (dataParseStatus != DataParseStatus.ContinueParsing)
                {
                    return dataParseStatus;
                }
                dataParseStatus = this.ParseWhitespaces(ref num);
                if (dataParseStatus != DataParseStatus.ContinueParsing)
                {
                    return dataParseStatus;
                }
                if (this.buffer[num] == 61)
                {
                    num++;
                    dataParseStatus = this.ParseWhitespaces(ref num);
                    if (dataParseStatus != DataParseStatus.ContinueParsing)
                    {
                        return dataParseStatus;
                    }
                    dataParseStatus = this.ParseToken(ref num);
                    if (dataParseStatus == DataParseStatus.Invalid)
                    {
                        dataParseStatus = this.ParseQuotedString(ref num);
                    }
                    if (dataParseStatus != DataParseStatus.ContinueParsing)
                    {
                        return dataParseStatus;
                    }
                    dataParseStatus = this.ParseWhitespaces(ref num);
                    if (dataParseStatus != DataParseStatus.ContinueParsing)
                    {
                        return dataParseStatus;
                    }
                }
            }
            pos = num;
            return DataParseStatus.ContinueParsing;
        }

        private DataParseStatus ParseQuotedString(ref int pos)
        {
            if (pos == this.bufferFillLength)
            {
                return DataParseStatus.NeedMoreData;
            }
            if (this.buffer[pos] != 34)
            {
                return DataParseStatus.Invalid;
            }
            for (int i = pos + 1; i < this.bufferFillLength; i++)
            {
                if (this.buffer[i] == 34)
                {
                    pos = i + 1;
                    return DataParseStatus.ContinueParsing;
                }
                if (this.buffer[i] == 92)
                {
                    i++;
                    if (i == this.bufferFillLength)
                    {
                        return DataParseStatus.NeedMoreData;
                    }
                    if (this.buffer[i] <= 127)
                    {
                        i++;
                        continue;
                    }
                }
            }
            return DataParseStatus.NeedMoreData;
        }

        private DataParseStatus ParseToken(ref int pos)
        {
            int i = pos;
            while (i < this.bufferFillLength)
            {
                if (!ChunkParser.IsTokenChar(this.buffer[i]))
                {
                    if (i > pos)
                    {
                        pos = i;
                        return DataParseStatus.ContinueParsing;
                    }
                    return DataParseStatus.Invalid;
                }
                else
                {
                    i++;
                }
            }
            return DataParseStatus.NeedMoreData;
        }

        private static bool IsTokenChar(byte character)
        {
            return character <= 127 && ChunkParser.tokenChars[(int)character];
        }
    }
}
